#include <common.cuh>
#include <cublas_v2.h>

namespace runtime {namespace cuda {


int init_device() {
    int device_count = 0;
    CHECK_CUDA_ERR(cudaGetDeviceCount(&device_count));
    if (!device_count) {
        std::cerr << "threre is no any cuda Devices on this machine! exit!" << std::endl;
        exit(-1);
    }
    int driverVersion=0,runtimeVersion=0;
    for (int dev = 0; dev < device_count; dev ++) {
        cudaSetDevice(dev);
        cudaDeviceProp deviceProp;
        cudaGetDeviceProperties(&deviceProp,dev);
        printf("Device %d:\"%s\"\n",dev,deviceProp.name);
        cudaDriverGetVersion(&driverVersion);
        cudaRuntimeGetVersion(&runtimeVersion);
        printf("  CUDA Driver Version / Runtime Version         %d.%d  /  %d.%d\n",
                driverVersion/1000,(driverVersion%100)/10,
                runtimeVersion/1000,(runtimeVersion%100)/10);
        printf("  CUDA Capability Major/Minor version number:   %d.%d\n",
                deviceProp.major,deviceProp.minor);
        printf("  Total amount of global memory:                %.2f GBytes (%lu bytes)\n",
                (float)deviceProp.totalGlobalMem/pow(1024.0,3),deviceProp.totalGlobalMem);
        printf("  GPU Clock rate:                               %.0f MHz (%0.2f GHz)\n",
                deviceProp.clockRate*1e-3f,deviceProp.clockRate*1e-6f);
        printf("  Memory Bus width:                             %d-bits\n",
                deviceProp.memoryBusWidth);
        if (deviceProp.l2CacheSize)
        {
                printf("  L2 Cache Size:                            	%d bytes\n",
                        deviceProp.l2CacheSize);
        }
        printf("  Max Texture Dimension Size (x,y,z)            1D=(%d),2D=(%d,%d),3D=(%d,%d,%d)\n",
                deviceProp.maxTexture1D,deviceProp.maxTexture2D[0],deviceProp.maxTexture2D[1]
                ,deviceProp.maxTexture3D[0],deviceProp.maxTexture3D[1],deviceProp.maxTexture3D[2]);
        printf("  Max Layered Texture Size (dim) x layers       1D=(%d) x %d,2D=(%d,%d) x %d\n",
                deviceProp.maxTexture1DLayered[0],deviceProp.maxTexture1DLayered[1],
                deviceProp.maxTexture2DLayered[0],deviceProp.maxTexture2DLayered[1],
                deviceProp.maxTexture2DLayered[2]);
        printf("  Total amount of constant memory               %lu bytes\n",
                deviceProp.totalConstMem);
        printf("  Total amount of shared memory per block:      %lu bytes\n",
                deviceProp.sharedMemPerBlock);
        printf("  Total number of registers available per block:%d\n",
                deviceProp.regsPerBlock);
        printf("  Wrap size:                                    %d\n",deviceProp.warpSize);
        printf("  Maximun number of thread per multiprocesser:  %d\n",
                deviceProp.maxThreadsPerMultiProcessor);
        printf("  Maximun number of thread per block:           %d\n",
                deviceProp.maxThreadsPerBlock);
        printf("  Maximun size of each dimension of a block:    %d x %d x %d\n",
                deviceProp.maxThreadsDim[0],deviceProp.maxThreadsDim[1],deviceProp.maxThreadsDim[2]);
        printf("  Maximun size of each dimension of a grid:     %d x %d x %d\n",
                deviceProp.maxGridSize[0],
                deviceProp.maxGridSize[1],
                deviceProp.maxGridSize[2]);
        printf("  Maximu memory pitch                           %lu bytes\n",deviceProp.memPitch);
        printf("----------------------------------------------------------\n");
        printf("Number of multiprocessors:                      %d\n", deviceProp.multiProcessorCount);
        printf("Total amount of constant memory:                %4.2f KB\n",
                deviceProp.totalConstMem/1024.0);
        printf("Total amount of shared memory per block:        %4.2f KB\n",
        deviceProp.sharedMemPerBlock/1024.0);
        printf("Total number of registers available per block:  %d\n",
        deviceProp.regsPerBlock);
        printf("Warp size                                       %d\n", deviceProp.warpSize);
        printf("Maximum number of threads per block:            %d\n", deviceProp.maxThreadsPerBlock);
        printf("Maximum number of threads per multiprocessor:  %d\n",
                deviceProp.maxThreadsPerMultiProcessor);
        printf("Maximum number of warps per multiprocessor:     %d\n",
                deviceProp.maxThreadsPerMultiProcessor/32);
    }
    // get mem info
    for (int dev = 0; dev < device_count; dev ++) {
        CHECK_CUDA_ERR(cudaSetDevice(dev));
        size_t totalMem = 0;
        size_t freeMem = 0;
        CHECK_CUDA_ERR(cudaMemGetInfo(&freeMem, &totalMem));
        if ((freeMem >> 20)) {
            CHECK_CUDA_ERR(cudaSetDevice(dev));
            std::cout << "use card " << dev << " to compute, avalibel Mem: " << (freeMem >> 20) << " MB.\n" << std::endl;
        }
    }

    return EXIT_SUCCESS;
}


const char* cublasGetErrorString(cublasStatus_t error) {
  switch (error) {
  case CUBLAS_STATUS_SUCCESS:
    return "CUBLAS_STATUS_SUCCESS";
  case CUBLAS_STATUS_NOT_INITIALIZED:
    return "CUBLAS_STATUS_NOT_INITIALIZED";
  case CUBLAS_STATUS_ALLOC_FAILED:
    return "CUBLAS_STATUS_ALLOC_FAILED";
  case CUBLAS_STATUS_INVALID_VALUE:
    return "CUBLAS_STATUS_INVALID_VALUE";
  case CUBLAS_STATUS_ARCH_MISMATCH:
    return "CUBLAS_STATUS_ARCH_MISMATCH";
  case CUBLAS_STATUS_MAPPING_ERROR:
    return "CUBLAS_STATUS_MAPPING_ERROR";
  case CUBLAS_STATUS_EXECUTION_FAILED:
    return "CUBLAS_STATUS_EXECUTION_FAILED";
  case CUBLAS_STATUS_INTERNAL_ERROR:
    return "CUBLAS_STATUS_INTERNAL_ERROR";
#if CUDA_VERSION >= 6000
  case CUBLAS_STATUS_NOT_SUPPORTED:
    return "CUBLAS_STATUS_NOT_SUPPORTED";
#endif
#if CUDA_VERSION >= 6050
  case CUBLAS_STATUS_LICENSE_ERROR:
    return "CUBLAS_STATUS_LICENSE_ERROR";
#endif
  }
  return "Unknown cublas status";
}

void* GpuMemManager::mapCpu2Gpu(void* cpuAddr, const int64_t byteSize) {
    if (!cpuAddr) {
        throw "Invalid cpuAddr!";
    }
    void* gpu_ptr;
    CHECK_CUDA_ERR(cudaMalloc((void**)(&gpu_ptr), byteSize));
    CHECK_CUDA_ERR(cudaMemcpy(gpu_ptr, cpuAddr, byteSize, cudaMemcpyHostToDevice));
    m_AddrTable.emplace_back(gpu_ptr);
    return gpu_ptr;
}

void GpuMemManager::mapGpu2Cpu(void* cpuAddr, void* gpuAddr, const int64_t byteSize) {
    if (!cpuAddr) {
        throw "Invalid cpuAddr!";
    }
    CHECK_CUDA_ERR(cudaMemcpy(cpuAddr, gpuAddr, byteSize, cudaMemcpyDeviceToHost));
}

GpuMemManager::~GpuMemManager() {
    for (auto ptr: m_AddrTable) {
        CHECK_CUDA_ERR(cudaFree(ptr));
    }
    m_AddrTable.clear();
}


}} // end of namespace runtime/cuda
